#include "stdafx.h"
#include "net/HTMLForm.hpp"
#include "net/HTTPRequest.hpp"
#include "net/PartHandler.hpp"
#include "net/PartSource.hpp"
#include "net/URI.hpp"
#include "net/MultipartWriter.hpp"
#include "net/MultipartReader.hpp"
#include "charset/utf8.hpp"
using JHCPP::charset::CUTF8;
#include "stdlib/exception.hpp"
using namespace JHCPP::stdlib::jhException;
#include "stream/streambuf.hpp"
#include "stream/StreamCopier.hpp"
using namespace JHCPP::stream;
#include <sstream>

NET_NAMESPACE_BEGIN

const std::string CHTMLForm::ENCODING_URL           = "application/x-www-form-urlencoded";
const std::string CHTMLForm::ENCODING_MULTIPART     = "multipart/form-data";
const int         CHTMLForm::UNKNOWN_CONTENT_LENGTH = -1;


class CHTMLFormCountingOutputStream : public CCountingOutputStream
{
public:
	CHTMLFormCountingOutputStream() : m_valid(true)
	{
	}

	bool isValid() const 
	{
		return m_valid;
	}

	void setValid(bool v)
	{
		m_valid = v;
	}

private:
	bool m_valid;
};


CHTMLForm::CHTMLForm()
	: m_fieldLimit(DFL_FIELD_LIMIT),
	m_encoding(ENCODING_URL)
{
}

CHTMLForm::CHTMLForm(const std::string& encoding)
	: m_fieldLimit(DFL_FIELD_LIMIT),
	m_encoding(encoding)
{
}

CHTMLForm::CHTMLForm(const CHTTPRequest& request, std::istream& requestBody, CPartHandler& handler) : m_fieldLimit(DFL_FIELD_LIMIT)
{
	load(request, requestBody, handler);
}

CHTMLForm::CHTMLForm(const CHTTPRequest& request, std::istream& requestBody) : m_fieldLimit(DFL_FIELD_LIMIT)
{
	load(request, requestBody);
}

CHTMLForm::CHTMLForm(const CHTTPRequest& request) : m_fieldLimit(DFL_FIELD_LIMIT)
{
	load(request);
}

CHTMLForm::~CHTMLForm()
{
	for (PartVec::iterator it = m_parts.begin(); it != m_parts.end(); ++it)
	{
		delete it->pSource;
	}
}

void CHTMLForm::setEncoding(const std::string& encoding)
{
	m_encoding = encoding;
}

const std::string& CHTMLForm::getEncoding() const
{
	return m_encoding;
}

const std::string& CHTMLForm::boundary() const
{
	return m_boundary;
}

void CHTMLForm::addPart(const std::string& name, CPartSource* pSource)
{
	jh_check_ptr (pSource);

	Part part;
	part.name    = name;
	part.pSource = pSource;
	m_parts.push_back(part);
}

void CHTMLForm::load(const CHTTPRequest& request, std::istream& requestBody, CPartHandler& handler)
{
	clear();

	CURI uri(request.getURI());
	const std::string& query = uri.getRawQuery();
	if (!query.empty())
	{
		std::istringstream istr(query);
		readUrl(istr);
	}

	if (request.getMethod() == CHTTPRequest::HTTP_POST || request.getMethod() == CHTTPRequest::HTTP_PUT)
	{
		std::string mediaType;
		CNameValueCollection params;
		CMessageHeader::splitParameters(request.getContentType(), mediaType, params); 
		m_encoding = mediaType;
		if (m_encoding == ENCODING_MULTIPART)
		{
			m_boundary = params["boundary"];
			readMultipart(requestBody, handler);
		}
		else
		{
			readUrl(requestBody);
		}
	}
}

void CHTMLForm::load(const CHTTPRequest& request, std::istream& requestBody)
{
	CNullPartHandler nah;
	load(request, requestBody, nah);
}

void CHTMLForm::load(const CHTTPRequest& request)
{
	CNullPartHandler nah;
	CNullInputStream nis;
	load(request, nis, nah);
}

void CHTMLForm::read(std::istream& istr, CPartHandler& handler)
{
	if (m_encoding == ENCODING_URL)
		readUrl(istr);
	else
		readMultipart(istr, handler);
}

void CHTMLForm::read(std::istream& istr)
{
	readUrl(istr);
}

void CHTMLForm::read(const std::string& queryString)
{
	std::istringstream istr(queryString);
	readUrl(istr);
}

void CHTMLForm::prepareSubmit(CHTTPRequest& request)
{
	if (request.getMethod() == CHTTPRequest::HTTP_POST || request.getMethod() == CHTTPRequest::HTTP_PUT)
	{
		if (m_encoding == ENCODING_URL)
		{
			request.setContentType(m_encoding);
			request.setChunkedTransferEncoding(false);
			CCountingOutputStream ostr;
			writeUrl(ostr);
			request.setContentLength(ostr.chars());
		}
		else
		{
			m_boundary = CMultipartWriter::createBoundary();
			std::string ct(m_encoding);
			ct.append("; boundary=\"");
			ct.append(m_boundary);
			ct.append("\"");
			request.setContentType(ct);
		}
		if (request.getVersion() == CHTTPMessage::HTTP_1_0)
		{
			request.setKeepAlive(false);
			request.setChunkedTransferEncoding(false);
		}
		else if (m_encoding != ENCODING_URL)
		{
			request.setChunkedTransferEncoding(true);
		}
	}
	else
	{
		std::string uri = request.getURI();
		std::ostringstream ostr;
		writeUrl(ostr);
		uri.append("?");
		uri.append(ostr.str());
		request.setURI(uri);
	}
}

std::streamsize CHTMLForm::calculateContentLength()
{
	if (m_boundary.empty())
		throw HTMLFormException("Form must be prepared");

	CHTMLFormCountingOutputStream c;
	write(c);
	if (c.isValid())
		return c.chars();
	else
		return UNKNOWN_CONTENT_LENGTH;
}

void CHTMLForm::write(std::ostream& ostr, const std::string& boundary)
{
	if (m_encoding == ENCODING_URL)
	{
		writeUrl(ostr);
	}
	else
	{
		m_boundary = boundary;
		writeMultipart(ostr);
	}
}


void CHTMLForm::write(std::ostream& ostr)
{
	if (m_encoding == ENCODING_URL)
		writeUrl(ostr);
	else
		writeMultipart(ostr);
}

void CHTMLForm::readUrl(std::istream& istr)
{
	static const int eof = std::char_traits<char>::eof();

	int fields = 0;
	int ch = istr.get();
	bool isFirst = true;
	while (ch != eof)
	{
		if (m_fieldLimit > 0 && fields == m_fieldLimit)
			throw HTMLFormException("Too many form fields");
		std::string name;
		std::string value;
		while (ch != eof && ch != '=' && ch != '&')
		{
			if (ch == '+') ch = ' ';
			name += (char) ch;
			ch = istr.get();
		}
		if (ch == '=')
		{
			ch = istr.get();
			while (ch != eof && ch != '&')
			{
				if (ch == '+') ch = ' ';
				value += (char) ch;
				ch = istr.get();
			}
		}
		// remove UTF-8 byte order mark from first name, if present
		if (isFirst)
		{
			CUTF8::removeBOM(name);
		}
		std::string decodedName;
		std::string decodedValue;
		CURI::decode(name, decodedName);
		CURI::decode(value, decodedValue);
		add(decodedName, decodedValue);
		++fields;
		if (ch == '&') ch = istr.get();
		isFirst = false;
	}
}

void CHTMLForm::readMultipart(std::istream& istr, CPartHandler& handler)
{
	static const int eof = std::char_traits<char>::eof();

	int fields = 0;
	CMultipartReader reader(istr, m_boundary);
	while (reader.hasNextPart())
	{
		if (m_fieldLimit > 0 && fields == m_fieldLimit)
			throw HTMLFormException("Too many form fields");
		CMessageHeader header;
		reader.nextPart(header);
		std::string disp;
		CNameValueCollection params;
		if (header.has("Content-Disposition"))
		{
			std::string cd = header.get("Content-Disposition");
			CMessageHeader::splitParameters(cd, disp, params);
		}
		if (params.has("filename"))
		{
			handler.handlePart(header, reader.stream());
			// Ensure that the complete part has been read.
			while (reader.stream().good()) reader.stream().get();
		}
		else
		{
			std::string name = params["name"];
			std::string value;
			std::istream& istr = reader.stream();
			int ch = istr.get();
			while (ch != eof)
			{
				value += (char) ch;
				ch = istr.get();
			}
			add(name, value);
		}
		++fields;
	}
}

void CHTMLForm::writeUrl(std::ostream& ostr)
{
	for (CNameValueCollection::ConstIterator it = begin(); it != end(); ++it)
	{
		if (it != begin()) ostr << "&";
		std::string name;
		CURI::encode(it->first, "!?#/'\",;:$&()[]*+=@", name);
		std::string value;
		CURI::encode(it->second, "!?#/'\",;:$&()[]*+=@", value);
		ostr << name << "=" << value;
	}
}

void CHTMLForm::writeMultipart(std::ostream& ostr)
{
	CHTMLFormCountingOutputStream *costr(dynamic_cast<CHTMLFormCountingOutputStream*>(&ostr));

	CMultipartWriter writer(ostr, m_boundary);
	for (CNameValueCollection::ConstIterator it = begin(); it != end(); ++it)
	{
		CMessageHeader header;
		std::string disp("form-data; name=\"");
		disp.append(it->first);
		disp.append("\"");
		header.set("Content-Disposition", disp);
		writer.nextPart(header);
		ostr << it->second;
	}	
	for (PartVec::iterator ita = m_parts.begin(); ita != m_parts.end(); ++ita)
	{
		CMessageHeader header(ita->pSource->headers());
		std::string disp("form-data; name=\"");
		disp.append(ita->name);
		disp.append("\"");
		std::string filename = ita->pSource->filename();
		if (!filename.empty())
		{
			disp.append("; filename=\"");
			disp.append(filename);
			disp.append("\"");
		}
		header.set("Content-Disposition", disp);
		header.set("Content-Type", ita->pSource->mediaType());
		writer.nextPart(header);
		if (costr)
		{
			// count only, don't move stream position
			std::streamsize partlen = ita->pSource->getContentLength();
			if (partlen != CPartSource::UNKNOWN_CONTENT_LENGTH)
				costr->addChars(static_cast<int>(partlen));
			else
				costr->setValid(false);
		}
		else
			CStreamCopier::copyStream(ita->pSource->stream(), ostr);
	}
	writer.close();
	m_boundary = writer.boundary();
}


void CHTMLForm::setFieldLimit(int limit)
{
	jh_assert (limit >= 0);

	m_fieldLimit = limit;
}

int CHTMLForm::getFieldLimit() const
{
	return m_fieldLimit;
}

NET_NAMESPACE_END
